﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using SlimDX;
using SlimDX.Direct3D9;

using System.Threading;
using System.Diagnostics;

namespace DiverseMeshTools
{
    public partial class Preview : Form
    {
        private Stopwatch _stopwatch;

        private Direct3D _direct3D;
        private Device _device;
        private PresentParameters _pp;

        private Matrix _view;
        private Matrix _projection;
        private Matrix _world;

        private Vector3 _eye;
        private Vector3 _target;
        private Vector3 _up;

        private float _fov;
        private float _aspect;
        private float _znear;
        private float _zfar;

        private float _maxMeshDiameter;

        private float _rotationY;

        private Mesh _mesh;
        private Mesh _optimized;

        private string _meshPath;
        private Matrix _transformation;

        private bool _draw;
        private bool _meshSwitch;
        private long _switchMs;

        private bool _skipOptimization;

        public Preview(string meshPath, Matrix transformation, bool skipOptimization)
        {
            _draw = false;
            _skipOptimization = skipOptimization;
            _switchMs = 15000;

            InitializeComponent();

            InitializeGraphics();

            _meshPath = meshPath;
            _transformation = transformation;

            _eye = new Vector3(0f, 5f, -14f);
            _target = Vector3.Zero;
            _up = Vector3.UnitY;

            _fov = (float)Math.PI / 3f;
            _aspect = (float)pictureBoxRenderTarget.Width / (float)pictureBoxRenderTarget.Height;
            _znear = 2f;
            _zfar = 500f;

            _stopwatch = new Stopwatch();
        }

        private void InitializeGraphics()
        {
            _pp = new PresentParameters();
            _pp.BackBufferWidth = pictureBoxRenderTarget.Width;
            _pp.BackBufferHeight = pictureBoxRenderTarget.Height;
            _pp.BackBufferFormat = Format.A8R8G8B8;

            _direct3D = new Direct3D();
            _device = new Device(_direct3D, 0, DeviceType.Hardware, pictureBoxRenderTarget.Handle, CreateFlags.HardwareVertexProcessing, _pp);

            ResetDeviceSettings();
        }

        private void ResetDeviceSettings()
        {
            _device.SetRenderState(RenderState.Lighting, true);
            _device.SetRenderState(RenderState.NormalizeNormals, true);

            Light light = new Light();

            light.Type = LightType.Directional;
            light.Direction = new Vector3(1f, -0.5f, 1f);
            light.Diffuse = Color.White;
            light.Ambient = Color.Gray;
            light.Specular = Color.Gray;

            _device.SetLight(0, light);
            _device.EnableLight(0, true);

            Material material = new Material();
            material.Diffuse = Color.White;
            material.Ambient = Color.Gray;

            _device.Material = material;
        }

        private void Preview_Load(object sender, EventArgs e)
        {
            // Load & DX otimize mesh
            _mesh = Mesh.FromFile(_device, _meshPath, MeshFlags.Managed);
            _mesh.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.Compact | MeshOptimizeFlags.AttributeSort);

            if (!_skipOptimization)
            {
                _optimized = D3D9MeshTools.Optimize(_mesh);
            }
            else
            {
                // For some reason Clone() fails -.- ... but this does the job much easier for me.
                _optimized = _mesh.Clean(CleanType.Optimization);
            }

            _optimized.OptimizeInPlace(MeshOptimizeFlags.VertexCache | MeshOptimizeFlags.Compact | MeshOptimizeFlags.AttributeSort);

            // TODO: Transform
            //_optimized = D3D9MeshTools.Transform(_optimized, _transformation);

            #region Find out and set a good view size ...
            BoundingBox meshBB = D3D9MeshTools.GetDimensions(_mesh);
            BoundingBox optimizedBB = D3D9MeshTools.GetDimensions(_optimized);

            Vector3 tempVector = meshBB.Maximum - meshBB.Minimum;
            float meshSize = tempVector.Length();

            tempVector = optimizedBB.Maximum - optimizedBB.Minimum;
            float optimizedSize = tempVector.Length();

            if (meshSize > optimizedSize)
            {
                _maxMeshDiameter = meshSize;
            }
            else
            {
                _maxMeshDiameter = optimizedSize;
            }

            _eye = new Vector3(0f, _maxMeshDiameter * 0.333333f, - _maxMeshDiameter * 1.333333f);
            tempVector = _eye;
            tempVector.Normalize();

            _eye = _eye + tempVector * _znear;

            _zfar = _eye.Length() + _maxMeshDiameter + 10f;
            #endregion

            _draw = true;
            _stopwatch.Start();

            timerDraw.Start();
        }

        #region Dispose On Form Closing
        private void Preview_FormClosing(object sender, FormClosingEventArgs e)
        {
            _stopwatch.Stop();
            timerDraw.Stop();

            #region Free memory
            if (_stopwatch != null)
            {
                if (_stopwatch.IsRunning)
                {
                    _stopwatch.Stop();
                }
                _stopwatch = null;
            }

            if (_mesh != null)
            {
                if (!_mesh.Disposed)
                {
                    _mesh.Dispose();
                }
                _mesh = null;
            }

            if (_optimized != null)
            {
                if (!_optimized.Disposed)
                {
                    _optimized.Dispose();
                }
                _optimized = null;
            }

            if (_device != null)
            {
                if (!_device.Disposed)
                {
                    _device.Dispose();
                }
                _device = null;
            }

            if (_direct3D != null)
            {
                if (!_direct3D.Disposed)
                {
                    _direct3D.Dispose();
                }
                _direct3D = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            #endregion
        }
        #endregion

        private void timerDraw_Tick(object sender, EventArgs e)
        {
            long msElapsed = _stopwatch.ElapsedMilliseconds;
            _stopwatch.Reset();
            _stopwatch.Start();

            if (_draw)
            {
                Result result = ResultCode.Success;
                string exceptionMessage = "";

                if (GetCooperativeLevel(out result))
                {
                    try
                    {
                        _device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.CornflowerBlue, 1.0f, 0);
                        _device.BeginScene();

                        // The rotation is irritating without a grid ...
                        //_rotationY += (float)Math.PI / 20f * ((float)msElapsed * 0.001f);

                        _switchMs += msElapsed;
                        if (_switchMs > 1500)
                        {
                            _meshSwitch = !_meshSwitch;
                            _switchMs = 0;

                            if (_meshSwitch)
                            {
                                this.Text = "Preview [ OPTIMIZED & TRANSFORMED ]";
                            }
                            else
                            {
                                this.Text = "Preview [ SOURCE ]";
                            }
                        }

                        #region Create and set view, proj, world matrices
                        _view = Matrix.LookAtLH(_eye, _target, _up);
                        _projection = Matrix.PerspectiveFovLH(_fov, _aspect, _znear, _zfar);
                        _world = Matrix.RotationY(_rotationY);

                        _device.SetTransform(TransformState.View, _view);
                        _device.SetTransform(TransformState.Projection, _projection);
                        _device.SetTransform(TransformState.World, _world);
                        #endregion

                        ExtendedMaterial[] exMat;
                        Material oldMaterial = _device.Material;

                        if (_meshSwitch)
                        {
                            exMat = _optimized.GetMaterials();

                            if (exMat.Length == 0)
                            {
                                _optimized.DrawSubset(0);
                            }
                            else
                            { 
                                for (int i = 0; i < exMat.Length; i++)
                                {
                                    _device.Material = exMat[i].MaterialD3D;
                                    _optimized.DrawSubset(i);
                                }
                            }
                        }
                        else
                        {
                            exMat = _mesh.GetMaterials();

                            if (exMat.Length == 0)
                            {
                                _mesh.DrawSubset(0);
                            }
                            else
                            {
                                for (int i = 0; i < exMat.Length; i++)
                                {
                                    _device.Material = exMat[i].MaterialD3D;
                                    _mesh.DrawSubset(i);
                                }
                            }
                        }

                        _device.Material = oldMaterial;

                        _device.EndScene();
                        _device.Present();
                    }
                    catch (Exception ex)
                    {
                        GetCooperativeLevel(out result);
                        exceptionMessage = ex.Message;
                    }
                }

                #region Handle device errors
                if (result.IsFailure)
                {
                    if (result.Code == ResultCode.DeviceNotReset.Code)
                    {
                        _device.Reset(_pp);
                        ResetDeviceSettings();
                    }
                    else if (result.Code == ResultCode.DeviceLost.Code)
                    {
                        Thread.Sleep(33);
                    }
                    else
                    {
                        MessageBox.Show(this, "Unexpected Error:\r\n\r\n" + exceptionMessage, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        this.Close();
                    }
                }
                #endregion
            }
        }

        #region CoopLevel
        public bool GetCooperativeLevel(out Result resultCode)
        {
            if (_device == null)
            {
                resultCode = ResultCode.Failure;
            }
            else
            {
                resultCode = _device.TestCooperativeLevel();
            }

            return resultCode.IsSuccess;
        }
        #endregion
    }
}
